package com.kancy.publicapi.aop.aspect;

import com.alibaba.fastjson.JSON;
import com.kancy.publicapi.aop.annotation.NotPrintLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * @author kancy
 * @version 1.0
 * @date 2019/4/2 10:51
 */
@Slf4j
@Order(5)
@Aspect
@Component
public class ApiLogAspect extends BaseAspect {
    private ThreadLocal<ApiLog> apiLogThreadLocal = new ThreadLocal<ApiLog>();
    /**
     * 声明切点
     *  @Pointcut("execution(* com.github.kancy.cloud.*.controller.*Controller.*(..))")
     *  @Pointcut("@annotation(com.github.kancy.cloud.aop.annotation.PrintLog)")
     */
    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *) " +
            "|| (within(@org.springframework.stereotype.Controller *) && @annotation(org.springframework.web.bind.annotation.ResponseBody)) ")
    public void pointCut() {

    }

    @Around("pointCut()")
    public final Object doPrintLog(ProceedingJoinPoint joinPoint) throws Throwable {
        // 若类或方法标有@NotPrintLog注解表示不打印日志
        boolean annotationPresent = joinPoint.getSignature().getDeclaringType().isAnnotationPresent(NotPrintLog.class);
        if (annotationPresent){
            return joinPoint.proceed();
        }
        MethodSignature methodSignature = (MethodSignature)joinPoint.getSignature();
        if (methodSignature.getMethod().isAnnotationPresent(NotPrintLog.class)){
            return joinPoint.proceed();
        }
        return check(joinPoint);
    }

    @Override
    protected Object check(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;
        try {
            apiLogThreadLocal.set(ApiLog.newInstance());
            doBefore(joinPoint);
            result = joinPoint.proceed(joinPoint.getArgs());
            doAfterReturning(result);
        }catch (Throwable e){
            doException(e);
            throw e;
        }finally {
            apiLogThreadLocal.remove();
        }
        return result;
    }

    private void doException(Throwable e) {
        // 处理完请求，返回内容
        ApiLog apiLog = apiLogThreadLocal.get();
        apiLog.setResponseBody("exception:"+e.getMessage());
        apiLog.setEndTime(System.currentTimeMillis());
        log.info(apiLog.toString());
    }

    private void doBefore(ProceedingJoinPoint joinPoint) {
        // 接收到请求，记录请求内容
        HttpServletRequest request = getRequest();
        // 记录下请求内容
        ApiLog apiLog = apiLogThreadLocal.get();
        apiLog.setApiClassName(joinPoint.getSignature().getDeclaringTypeName());
        apiLog.setApiMethodName(joinPoint.getSignature().getName());
        apiLog.setRequestBody(JSON.toJSONString(joinPoint.getArgs()));
        apiLog.setRequestMethod(request.getMethod());
        apiLog.setRequestIp(getIpAddr());
        apiLog.setRequestUrl(request.getRequestURL().toString());
    }

    /**
     * 获取方法参数信息
     * @param method 方法
     * @param parameterIndex 参数序号
     * @return {MethodParameter}
     */
    private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
    public MethodParameter getMethodParameter(Method method, int parameterIndex) {
        MethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex);
        methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER);
        return methodParameter;
    }
    private void doAfterReturning(Object result) {
        // 处理完请求，返回内容
        ApiLog apiLog = apiLogThreadLocal.get();
        apiLog.setResponseBody(JSON.toJSONString(result));
        apiLog.setEndTime(System.currentTimeMillis());
        log.info(apiLog.toString());
    }


}
